package org.mybatis.enhance;

import com.kaka.util.ReflectUtils;
import com.kaka.util.StringUtils;
import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.binding.MapperProxyFactory;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.scripting.xmltags.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.enhance.annotation.PrimaryKey;
import org.mybatis.enhance.annotation.Table;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.locks.StampedLock;

/**
 * @author zkpursuit
 */
public class MybatisMapperRegistry extends MapperRegistry {
    private final Configuration conf;
    private final Map<Class<?>, MapperProxyFactory<?>> _knownMappers;
    protected Set<Class<?>> registerRecords = Collections.synchronizedSet(new HashSet<>());
    private final StampedLock lock = new StampedLock();

    @SuppressWarnings("unchecked")
    public MybatisMapperRegistry(Configuration config) {
        super(config);
        this.conf = config;
        Object knownMappersObj = ReflectUtils.getFieldValue(this, "knownMappers");
        this._knownMappers = (Map<Class<?>, MapperProxyFactory<?>>) knownMappersObj;
    }

    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) _knownMappers.get(type);
        if (mapperProxyFactory == null) {
            if (Mapper.class.isAssignableFrom(type)) {
                long stamp = lock.writeLock();
                try {
                    this.addMapper(type, false);
                    this.injectMappedStatement(type);
                    mapperProxyFactory = (MybatisMapperProxyFactory<T>) _knownMappers.get(type);
                } finally {
                    lock.unlock(stamp);
                }
            } else {
                throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
            }
        } else if (Mapper.class.isAssignableFrom(type)) {
            long stamp = lock.writeLock();
            try {
                this.injectMappedStatement(type);
            } finally {
                lock.unlock(stamp);
            }
        }
        try {
            return getMapper(mapperProxyFactory, sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

    protected <T> T getMapper(MybatisMapperProxyFactory<T> mapperProxyFactory, SqlSession sqlSession) {
        return mapperProxyFactory.newInstance(sqlSession);
    }

    protected <T> void addMapper(Class<T> type, boolean parseMapperAnnotation) {
        if (!type.isInterface()) return;
        if (hasMapper(type)) {
            return;
            //throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            _knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            if (parseMapperAnnotation) {
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(conf, type);
                parser.parse();
            }
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                _knownMappers.remove(type);
            }
        }
    }

    public <T> void addMapper(Class<T> type) {
        this.addMapper(type, true);
    }

    protected Class<?> getGenericClass(Class mapperInterfaceClass) {
        Type[] types = mapperInterfaceClass.getGenericInterfaces();
        Class<?> entityClass = null;
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                ParameterizedType t = (ParameterizedType) type;
                //判断父接口是否为 SelectMapper.class
                if (t.getRawType() == Mapper.class) {
                    //得到泛型类型
                    entityClass = (Class<?>) t.getActualTypeArguments()[0];
                    break;
                }
            }
        }
        return entityClass;
    }

    /**
     * 是否为基本类型
     *
     * @param cls
     * @return
     */
    protected boolean isPrimitive(Class cls) {
        if (cls == int.class || cls == Integer.class) return true;
        if (cls == boolean.class || cls == Boolean.class) return true;
        if (cls == short.class || cls == Short.class) return true;
        if (cls == long.class || cls == Long.class) return true;
        if (cls == float.class || cls == Float.class) return true;
        if (cls == double.class || cls == Double.class) return true;
        if (cls == byte.class || cls == Byte.class) return true;
        if (cls == char.class || cls == Character.class) return true;
        return cls == void.class;
    }

    /**
     * 注入 MappedStatement
     *
     * @param mapperInterfaceClass
     */
    public void injectMappedStatement(Class mapperInterfaceClass) {
        if (registerRecords.contains(mapperInterfaceClass)) return;
        if (!Mapper.class.isAssignableFrom(mapperInterfaceClass)) return;
        registerRecords.add(mapperInterfaceClass);
        String fullClassName = mapperInterfaceClass.getTypeName();
        Class<?> entityClass = getGenericClass(mapperInterfaceClass);
        Table table = entityClass.getAnnotation(Table.class);
        String tableName = table.value();
        Field[] entityFields = ReflectUtils.getDeclaredFields(entityClass);
        Class<?> entityPrimaryKeyClass = null;
        String entityPrimaryKeyName = null;
        boolean autoIncrementPrimaryKey = false;
        for (Field field : entityFields) {
            PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
            if (primaryKey != null) {
                entityPrimaryKeyClass = field.getType();
                entityPrimaryKeyName = field.getName();
                autoIncrementPrimaryKey = primaryKey.autoIncrement();
                break;
            }
        }
        Method[] methods = mapperInterfaceClass.getMethods();
        for (Method method : methods) {
            if (method.getDeclaringClass() == Mapper.class) {
                String methodName = method.getName();
                String mappedStatementId = fullClassName + "." + methodName;
                MappedStatement ms = null;
                String sql;
                if (methodName.equals("selectById")) {
                    sql = "select * from " + tableName + " where " + entityPrimaryKeyName + "=?";
                    List<ParameterMapping> parameterMappingList = new ArrayList<>(1);
                    ParameterMapping pm = new ParameterMapping.Builder(this.conf, entityPrimaryKeyName, entityPrimaryKeyClass).build();
                    parameterMappingList.add(pm);
                    SqlSource sqlSource = buildStaticSqlSource(sql, Collections.unmodifiableList(parameterMappingList));
                    ms = createMappedStatement(mappedStatementId, entityClass, Serializable.class, method, sqlSource, SqlCommandType.SELECT, null);
                } else if (methodName.equals("selectAll")) {
                    sql = "select * from " + tableName;
                    SqlSource sqlSource = buildStaticSqlSource(sql, Collections.emptyList());
                    ms = createMappedStatement(mappedStatementId, entityClass, entityPrimaryKeyClass, method, sqlSource, SqlCommandType.SELECT, null);
                } else if (methodName.equals("selectByIds")) {
                    sql = "select * from " + tableName + " where " + entityPrimaryKeyName + " in ";
                    SqlSource sqlSource = buildForeachSqlSource(sql, "#{item}", "(", ")");
                    ms = createMappedStatement(mappedStatementId, entityClass, List.class, method, sqlSource, SqlCommandType.SELECT, null);
                } else if (methodName.equals("insertOne")) {
                    sql = "insert into {} ({}) values ({})";
                    StringBuilder sb1 = new StringBuilder();
                    StringBuilder sb2 = new StringBuilder();
                    List<ParameterMapping> parameterMappingList = new ArrayList<>(entityFields.length);
                    for (int i = 0; i < entityFields.length; i++) {
                        Field field = entityFields[i];
                        String fieldName = field.getName();
                        boolean flag = false;
                        if (!autoIncrementPrimaryKey) {
                            flag = true;
                        } else if (!fieldName.equals(entityPrimaryKeyName)) {
                            flag = true;
                        }
                        if (flag) {
                            sb1.append(fieldName);
                            sb2.append('?');
                            ParameterMapping pm = new ParameterMapping.Builder(this.conf, fieldName, field.getType()).build();
                            parameterMappingList.add(pm);
                            if (i != entityFields.length - 1) {
                                sb1.append(", ");
                                sb2.append(", ");
                            }
                        }
                    }
                    sql = StringUtils.replace(sql, new Object[]{tableName, sb1.toString(), sb2.toString()});
                    SqlSource sqlSource = buildStaticSqlSource(sql, Collections.unmodifiableList(parameterMappingList));
                    String autoIncrementId = null;
                    if (autoIncrementPrimaryKey) {
                        autoIncrementId = entityPrimaryKeyName;
                    }
                    ms = createMappedStatement(mappedStatementId, entityClass, entityClass, method, sqlSource, SqlCommandType.INSERT, autoIncrementId);
                } else if (methodName.equals("batchInsert")) {
                    sql = "insert into {} ({}) values ";
                    StringBuilder sb1 = new StringBuilder();
                    StringBuilder sb2 = new StringBuilder();
                    for (int i = 0; i < entityFields.length; i++) {
                        Field field = entityFields[i];
                        String fieldName = field.getName();
                        boolean flag = false;
                        if (!autoIncrementPrimaryKey) {
                            flag = true;
                        } else if (!fieldName.equals(entityPrimaryKeyName)) {
                            flag = true;
                        }
                        if (flag) {
                            sb1.append(fieldName).append(',');
                            sb2.append("#{").append("item.").append(fieldName).append("},");
                        }
                    }
                    sb1.delete(sb1.length() - 1, sb1.length());
                    sb2.delete(sb2.length() - 1, sb2.length());
                    sql = StringUtils.replace(sql, new Object[]{tableName, sb1.toString()});
                    SqlSource sqlSource = buildForeachSqlSource(sql, " (" + sb2.toString() + ") ", "", "");
                    String autoIncrementId = null;
                    if (autoIncrementPrimaryKey) {
                        autoIncrementId = entityPrimaryKeyName;
                    }
                    ms = createMappedStatement(mappedStatementId, entityClass, List.class, method, sqlSource, SqlCommandType.INSERT, autoIncrementId);
                } else if (methodName.equals("updateById")) {
                    sql = "update {} set {} where {}=?";
                    StringBuilder sb1 = new StringBuilder();
                    List<ParameterMapping> parameterMappingList = new ArrayList<>(entityFields.length);
                    for (int i = 0; i < entityFields.length; i++) {
                        Field field = entityFields[i];
                        if (!field.getName().equalsIgnoreCase(entityPrimaryKeyName)) {
                            sb1.append(field.getName()).append("=?").append(',');
                            ParameterMapping pm = new ParameterMapping.Builder(this.conf, field.getName(), field.getType()).build();
                            parameterMappingList.add(pm);
                        }
                    }
                    sb1.delete(sb1.length() - 1, sb1.length());
                    ParameterMapping pm = new ParameterMapping.Builder(this.conf, entityPrimaryKeyName, entityPrimaryKeyClass).build();
                    parameterMappingList.add(pm);
                    sql = StringUtils.replace(sql, new Object[]{tableName, sb1.toString(), entityPrimaryKeyName});
                    SqlSource sqlSource = buildStaticSqlSource(sql, Collections.unmodifiableList(parameterMappingList));
                    ms = createMappedStatement(mappedStatementId, entityClass, entityClass, method, sqlSource, SqlCommandType.UPDATE, null);
                } else if (methodName.equals("updateFieldsById")) {
                    sql = "update " + tableName + " set ";
                    SqlSource sqlSource = buildMapSqlSource(sql, entityPrimaryKeyName);
                    ms = createMappedStatement(mappedStatementId, entityClass, Map.class, method, sqlSource, SqlCommandType.UPDATE, null);
                } else if (methodName.equals("updateNotNullFieldsById")) {
                    sql = "update " + tableName + " ";
                    SqlSource sqlSource = buildUpdateNotNullFieldsByIdSqlSource(sql, "", entityFields);
                    ms = createMappedStatement(mappedStatementId, entityClass, entityClass, method, sqlSource, SqlCommandType.UPDATE, null);
                } else if (methodName.equals("deleteById")) {
                    sql = "delete from " + tableName + " where " + entityPrimaryKeyName + "=?";
                    List<ParameterMapping> parameterMappingList = new ArrayList<>(1);
                    ParameterMapping pm = new ParameterMapping.Builder(this.conf, entityPrimaryKeyName, entityPrimaryKeyClass).build();
                    parameterMappingList.add(pm);
                    SqlSource sqlSource = buildStaticSqlSource(sql, Collections.unmodifiableList(parameterMappingList));
                    ms = createMappedStatement(mappedStatementId, entityClass, Serializable.class, method, sqlSource, SqlCommandType.DELETE, null);
                } else if (methodName.equals("deleteByIds")) {
                    sql = "delete from " + tableName + " where " + entityPrimaryKeyName + " in ";
                    SqlSource sqlSource = buildForeachSqlSource(sql, "#{item}", "(", ")");
                    ms = createMappedStatement(mappedStatementId, entityClass, List.class, method, sqlSource, SqlCommandType.DELETE, null);
                } else if (methodName.equals("selectCountByCondition")) {
                    SqlSource sqlSource = buildSelectCountByConditionSqlSource(tableName);
                    ms = createMappedStatement(mappedStatementId, entityClass, Condition.class, method, sqlSource, SqlCommandType.SELECT, null);
                } else if (methodName.equals("selectListByCondition")) {
                    SqlSource sqlSource = buildSelectListByConditionSqlSource(tableName);
                    ms = createMappedStatement(mappedStatementId, entityClass, Condition.class, method, sqlSource, SqlCommandType.SELECT, null);
                } else if (methodName.equals("selectOneByCondition")) {
                    SqlSource sqlSource = buildSelectListByConditionSqlSource(tableName);
                    ms = createMappedStatement(mappedStatementId, entityClass, Condition.class, method, sqlSource, SqlCommandType.SELECT, null);
                } else if (methodName.equals("updateByCondition")) {
                    SqlSource sqlSource = buildUpdateByConditionSqlSource(tableName, entityFields);
                    ms = createMappedStatement(mappedStatementId, entityClass, Condition.class, method, sqlSource, SqlCommandType.UPDATE, null);
                } else if (methodName.equals("deleteByCondition")) {
                    SqlSource sqlSource = buildDeleteByConditionSqlSource(tableName);
                    ms = createMappedStatement(mappedStatementId, entityClass, Condition.class, method, sqlSource, SqlCommandType.DELETE, null);
                }
                if (ms != null && !this.conf.hasStatement(mappedStatementId)) {
                    this.conf.addMappedStatement(ms);
                }
            }
        }
    }

    /**
     * 根据方法参数Class创建ParameterMapping对象
     *
     * @param paramType 方法参数Class
     * @param list      ParameterMapping存储列表
     */
    protected void buildParameterMapping(Class<?> paramType, List<ParameterMapping> list) {
        Field[] fields = ReflectUtils.getDeclaredFields(paramType);
        for (Field field : fields) {
            ParameterMapping parameterMapping = new ParameterMapping.Builder(this.conf, field.getName(), field.getType()).build();
            list.add(parameterMapping);
        }
    }

    protected SqlSource buildStaticSqlSource(String sql, List<ParameterMapping> parameterMappingList) {
        return new StaticSqlSource(this.conf, sql, parameterMappingList);
    }

    protected SqlSource buildForeachSqlSource(String sql, String foreachText, String foreachOpen, String foreachClose) {
        List<SqlNode> sqlNodeList = new ArrayList<>();
        sqlNodeList.add(new StaticTextSqlNode(sql));
        List<SqlNode> forEachSqlNodeContentChildren = new ArrayList<>(1);
        forEachSqlNodeContentChildren.add(new StaticTextSqlNode(foreachText));
        SqlNode forEachSqlNodeContent = new MixedSqlNode(forEachSqlNodeContentChildren);
        sqlNodeList.add(new ForEachSqlNode(this.conf, forEachSqlNodeContent, "list", "index", "item", foreachOpen, foreachClose, ","));
        SqlNode rootSqlNode = new MixedSqlNode(sqlNodeList);
        DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(this.conf, rootSqlNode);
        return dynamicSqlSource;
    }

    protected SqlSource buildUpdateNotNullFieldsByIdSqlSource(String sql, String whereSql, Field[] entityFields) {
        List<SqlNode> rootSqlNodeContents = new ArrayList<>();
        rootSqlNodeContents.add(new StaticTextSqlNode(sql));
        List<SqlNode> setSqlNodeContentChildren = new ArrayList<>();
        for (Field field : entityFields) {
            PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
            if (primaryKey != null) {
                if (whereSql == null || "".equals(whereSql)) {
                    whereSql = " where " + field.getName() + "=#{" + field.getName() + "} ";
                }
            } else {
                String test = field.getName() + "!=null";
                String ifContent = field.getName() + "=#{" + field.getName() + "},";
                SqlNode ifSqlNodeContent = new TextSqlNode(ifContent);
                IfSqlNode ifSqlNode = new IfSqlNode(ifSqlNodeContent, test);
                setSqlNodeContentChildren.add(ifSqlNode);
            }
        }
        SqlNode setSqlNodeContent = new MixedSqlNode(setSqlNodeContentChildren);
        rootSqlNodeContents.add(new SetSqlNode(this.conf, setSqlNodeContent));
        rootSqlNodeContents.add(new StaticTextSqlNode(whereSql));
        SqlNode rootSqlNode = new MixedSqlNode(rootSqlNodeContents);
        DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(this.conf, rootSqlNode);
        return dynamicSqlSource;
    }

    protected SqlSource buildMapSqlSource(String sql, String primaryKey) {
        List<SqlNode> rootSqlNodeContents = new ArrayList<>(4);
        rootSqlNodeContents.add(new StaticTextSqlNode(sql));
        IfSqlNode ifSqlNode = new IfSqlNode(new TextSqlNode(" ${key}=#{value} "), "key!='" + primaryKey + "'");
        rootSqlNodeContents.add(new ForEachSqlNode(conf, ifSqlNode, "params", "key", "value", null, null, ","));
        rootSqlNodeContents.add(new TextSqlNode(" where " + primaryKey + "=#{params." + primaryKey + "} "));
        SqlNode rootSqlNode = new MixedSqlNode(rootSqlNodeContents);
        DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(this.conf, rootSqlNode);
        return dynamicSqlSource;
    }

    /**
     * 构建条件查询选择字段
     *
     * @return
     */
    private SqlNode buildConditionSelectFields() {
        List<SqlNode> chooseIfNodes = new ArrayList<>(2);
        SqlNode chooseIfNodeContent = new ForEachSqlNode(this.conf, new TextSqlNode("${item}"), "condition.selectFields", null, "item", null, null, ",");
        chooseIfNodes.add(new IfSqlNode(chooseIfNodeContent, "condition.selectFields!=null"));
        SqlNode chooseNode = new ChooseSqlNode(chooseIfNodes, new StaticTextSqlNode("*"));
        return chooseNode;
    }

    /**
     * 构建条件查询where
     *
     * @return
     */
    protected SqlNode buildConditionWhere() {
        List<SqlNode> whereIfNodeContents = new ArrayList<>(2);
        whereIfNodeContents.add(new StaticTextSqlNode("where"));
        List<SqlNode> foreachNodeContents = new ArrayList<>(10);
        foreachNodeContents.add(new IfSqlNode(new StaticTextSqlNode("("), "item.id==1000"));
        foreachNodeContents.add(new IfSqlNode(new StaticTextSqlNode(")"), "item.id==2000"));
        foreachNodeContents.add(new IfSqlNode(new TextSqlNode("${item.field}<#{item.value}"), "item.id==2"));
        foreachNodeContents.add(new IfSqlNode(new TextSqlNode("${item.field}>#{item.value}"), "item.id==3"));
        foreachNodeContents.add(new IfSqlNode(new StaticTextSqlNode("and"), "item.id==100"));
        foreachNodeContents.add(new IfSqlNode(new StaticTextSqlNode("or"), "item.id==200"));
        foreachNodeContents.add(new IfSqlNode(new TextSqlNode("${item.field}=#{item.value}"), "item.id==4"));
        foreachNodeContents.add(new IfSqlNode(new TextSqlNode("${item.field}<>#{item.value}"), "item.id==5"));
        foreachNodeContents.add(new IfSqlNode(new TextSqlNode("${item.field} in ${item.value}"), "item.id==6"));
        whereIfNodeContents.add(new ForEachSqlNode(this.conf, new MixedSqlNode(foreachNodeContents), "condition.items", null, "item", null, null, null));
        return new IfSqlNode(new MixedSqlNode(whereIfNodeContents), "!condition.items.isEmpty()");
    }

    /**
     * 构建条件查询排序
     *
     * @return
     */
    protected SqlNode buildConditionOrder() {
        return new IfSqlNode(new TextSqlNode(" order by ${condition.order.field} ${condition.order.type} "), "condition.order!=null");
    }

    protected SqlSource buildSelectCountByConditionSqlSource(String table) {
        List<SqlNode> rootSqlNodeContents = new ArrayList<>(4);
        rootSqlNodeContents.add(new StaticTextSqlNode("select "));
        rootSqlNodeContents.add(buildConditionSelectFields());
        rootSqlNodeContents.add(new StaticTextSqlNode(" from " + table));
        rootSqlNodeContents.add(buildConditionWhere());
        SqlNode rootSqlNode = new MixedSqlNode(rootSqlNodeContents);
        return new DynamicSqlSource(this.conf, rootSqlNode);
    }

    protected SqlSource buildSelectListByConditionSqlSource(String table) {
        List<SqlNode> rootSqlNodeContents = new ArrayList<>(5);
        rootSqlNodeContents.add(new StaticTextSqlNode("select "));
        rootSqlNodeContents.add(buildConditionSelectFields());
        rootSqlNodeContents.add(new StaticTextSqlNode(" from " + table));
        rootSqlNodeContents.add(buildConditionWhere());
        rootSqlNodeContents.add(buildConditionOrder());
        SqlNode rootSqlNode = new MixedSqlNode(rootSqlNodeContents);
        return new DynamicSqlSource(this.conf, rootSqlNode);
    }

    protected SqlSource buildDeleteByConditionSqlSource(String table) {
        List<SqlNode> rootSqlNodeContents = new ArrayList<>(2);
        rootSqlNodeContents.add(new StaticTextSqlNode("delete from " + table + " "));
        rootSqlNodeContents.add(buildConditionWhere());
        SqlNode rootSqlNode = new MixedSqlNode(rootSqlNodeContents);
        return new DynamicSqlSource(this.conf, rootSqlNode);
    }

    protected SqlSource buildUpdateByConditionSqlSource(String table, Field[] entityFields) {
        List<SqlNode> rootSqlNodeContents = new ArrayList<>(4);
        rootSqlNodeContents.add(new StaticTextSqlNode("update " + table + " "));
        List<SqlNode> setSqlNodeContentChildren = new ArrayList<>();
        for (Field field : entityFields) {
            PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
            if (primaryKey == null) {
                String test = "entity." + field.getName() + "!=null";
                String ifContent = field.getName() + "=#{entity." + field.getName() + "},";
                SqlNode ifSqlNodeContent = new TextSqlNode(ifContent);
                IfSqlNode ifSqlNode = new IfSqlNode(ifSqlNodeContent, test);
                setSqlNodeContentChildren.add(ifSqlNode);
            }
        }
        SqlNode setSqlNodeContent = new MixedSqlNode(setSqlNodeContentChildren);
        rootSqlNodeContents.add(new SetSqlNode(this.conf, setSqlNodeContent));
        rootSqlNodeContents.add(buildConditionWhere());
        SqlNode rootSqlNode = new MixedSqlNode(rootSqlNodeContents);
        DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(this.conf, rootSqlNode);
        return dynamicSqlSource;
    }

    protected ParameterMap builderParameterMap(String parameterMapId, Method mapperInterfaceMethod, Class<?> parameterMapType, Class<?> entityClass) {
        Class[] methodParamTypes = mapperInterfaceMethod.getParameterTypes();
        if (methodParamTypes.length != 1) {
            return new ParameterMap.Builder(this.conf, "defaultParameterMap", null, Collections.emptyList()).build();
        }
        Class<?> paramType = methodParamTypes[0];
        if (isPrimitive(paramType)) {
            return new ParameterMap.Builder(this.conf, parameterMapId, paramType, Collections.emptyList()).build();
        }
        if (paramType == Serializable.class) {
            return new ParameterMap.Builder(this.conf, parameterMapId, parameterMapType, Collections.emptyList()).build();
        }
        if (paramType == entityClass || paramType == Object.class) {
            List<ParameterMapping> list = new ArrayList<>();
            buildParameterMapping(entityClass, list);
            return new ParameterMap.Builder(this.conf, parameterMapId, parameterMapType, Collections.unmodifiableList(list)).build();
        }
        return new ParameterMap.Builder(this.conf, parameterMapId, parameterMapType, Collections.emptyList()).build();
    }

    protected MappedStatement createMappedStatement(String statementId, Class<?> entityClass, Class<?> parameterMapType
            , Method method, SqlSource sqlSource, SqlCommandType commandType, String autoIncrementId) {
        final String parameterMapId = statementId + "-Custom";
        ParameterMap paramMap = builderParameterMap(parameterMapId, method, parameterMapType, entityClass);
        List<ResultMap> resultMaps;
        Class<?> returnType = method.getReturnType();
        if (returnType != void.class) {
            final String resultMapId = parameterMapId;
            Class<?> resultMapType;
            if (!isPrimitive(returnType)) {
                resultMapType = entityClass;
            } else {
                resultMapType = returnType;
            }
            ResultMap resultMap = new ResultMap.Builder(this.conf, resultMapId, resultMapType, Collections.emptyList()).build();
            resultMaps = Collections.singletonList(resultMap);
        } else {
            resultMaps = Collections.emptyList();
        }
        MappedStatement.Builder builder = new MappedStatement.Builder(this.conf, statementId, sqlSource, commandType)
                .databaseId(this.conf.getDatabaseId())
                .statementType(StatementType.PREPARED)
                .parameterMap(paramMap)
                .resultMaps(resultMaps)
                .resource("custom-auto-build")
                .fetchSize(null)
                .timeout(null)
                .cache(null)
                .flushCacheRequired(true)
                .useCache(false)
                .resultOrdered(false)
                .keyColumn(null)
                .keyProperty(null)
                .lang(new XMLLanguageDriver());
        if (commandType == SqlCommandType.INSERT && autoIncrementId != null) {
            builder.keyGenerator(Jdbc3KeyGenerator.INSTANCE);
            builder.keyProperty(autoIncrementId);
        } else {
            builder.keyGenerator(new NoKeyGenerator());
            builder.keyProperty(null);
        }
        MappedStatement ms = builder.build();
        return ms;
    }

}

